package com.sinosoft.security.hyde;

import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;


/**
 * RSA加解密算法：
 * Base64编码、解码采用commons-codec(1.9版本)中Base64类；
 *  * 描述:
 *  *
 *  @author 张旭东
 *  @date 2018/11/30
 */
public class RSA {

    private static final String KEY_ALGORITHM = "RSA";
    private static final int ENCRYPT_BLOCK_SIZE = 245; //通过/token.json?access_token=接口获取到的encryptBlockSize字段值进行赋值;
    private static final int DECRYPT_BLOCK_SIZE = 256; //通过/token.json?access_token=接口获取到的decryptBlockSize字段值进行赋值；

    /**
     * 公钥加密
     *
     * @param data
     *            待加密数据
     * @param publicKey
     *            公钥
     * @return String 加密数据
     * @throws Exception
     */
    public static String encryptByPublicKey(String data, String publicKey) throws Exception {
        byte[] encryptData = encryptByPublicKey(Base64.decodeBase64(data), Base64.decodeBase64(publicKey));
        return Base64.encodeBase64String(encryptData);
    }

    /**
     * 公钥加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            公钥
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLength = data.length;
        int inputOffset = 0, i = 0;
        int blockSize = ENCRYPT_BLOCK_SIZE;
        ByteArrayOutputStream bArrayOutputStream = new ByteArrayOutputStream();
        while (inputLength - inputOffset > 0) {
            byte[] cache;
            if (inputLength - inputOffset > blockSize) {
                cache = cipher.doFinal(data, inputOffset, blockSize);
            } else {
                cache = cipher.doFinal(data, inputOffset, inputLength - inputOffset);
            }
            bArrayOutputStream.write(cache);
            i++;
            inputOffset = i * blockSize;
        }
        byte[] encryptData = bArrayOutputStream.toByteArray();
        bArrayOutputStream.close();
        return encryptData;
    }

    /**
     * 公钥解密
     *
     * @param data
     *            待解密数据
     * @param publicKey
     *            公钥
     * @return String 解密数据
     * @throws Exception
     */
    public static String decryptByPublicKey(String data, String publicKey) throws Exception {
        byte[] decryptData = decryptByPublicKey(Base64.decodeBase64(data), Base64.decodeBase64(publicKey));
        return Base64.encodeBase64String(decryptData);
    }

    /**
     * 公钥解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            公钥
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        int inputLength = data.length;
        int inputOffset = 0, i = 0;
        int blockSize = DECRYPT_BLOCK_SIZE;
        ByteArrayOutputStream bArrayOutputStream = new ByteArrayOutputStream();
        while (inputLength - inputOffset > 0) {
            byte[] cache;
            if (inputLength - inputOffset > blockSize) {
                cache = cipher.doFinal(data, inputOffset, blockSize);
            } else {
                cache = cipher.doFinal(data, inputOffset, inputLength - inputOffset);
            }
            bArrayOutputStream.write(cache);
            i++;
            inputOffset = i * blockSize;
        }
        byte[] encryptData = bArrayOutputStream.toByteArray();
        bArrayOutputStream.close();
        return encryptData;
    }

}

